/*
 * 代号：凤凰
 * http://www.jphenix.org
 * 2015年10月15日
 * V4.0
 */
package com.jphenix.kernel.script;

import com.jphenix.kernel.baseobject.instanceb.ABase;
import com.jphenix.share.util.BaseUtil;
import com.jphenix.share.util.SFilesUtil;
import com.jphenix.standard.docs.BeanInfo;
import com.jphenix.standard.docs.ClassInfo;
import com.jphenix.standard.script.IScriptLoader;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 脚本文件检查类
 * 
 * 轮询检查新增的脚本，删除的脚本，修改的脚本
 * com.jphenix.kernel.script.ScriptCheckService
 * 
 * 2018-05-18 增加了启动和停止日志，并将该类放在配置文件中默认启动
 * 2018-12-12 简化了操作步骤
 * 2019-07-06 增加了构造函数，让脚本加载器直接构造并启动
 * 2019-09-25 整理了代码格式
 * 2019-12-20 修改了万一出现脚本代码重复的情况，避免出现频繁提示脚本发生变化
 * 2020-03-04 修改了检查策略，只有在启动时检查脚本源文件是否被删除。否则在运行时移动脚本，会被认为删除了，结果导致真被删除了
 * 2020-08-25 增加了是否允许执行检测任务方法
 * 
 * @author 马宝刚
 * 2015年10月15日
 */
@ClassInfo({"2020-08-25 16:54","脚本文件检查类"})
@BeanInfo({"scriptcheckservice"})
public class ScriptCheckService extends ABase {

    //脚本文件信息序列
    private Map<String,ScriptFileBean> fileMap     = new HashMap<String,ScriptFileBean>();
    private IScriptLoader              sl          = null;  //脚本管理器
    private long                       checkTime   = 10000; //轮询检测间隔时间
    private CheckThread                ct          = null;  //检测线程
    private boolean                    isRun       = false; //是否正在运行
    private boolean                    enabled     = true;  //是否允许执行检测任务
    
    
    /**
     * 检测线程
     * @author 马宝刚
     * 2015年10月15日
     */
    protected class CheckThread extends Thread {
        
        /**
         * 构造函数
         * @author 马宝刚
         */
        public CheckThread() {
            super("ScriptCheckService-CheckThread");
        }
        
        /**
         * 覆盖函数
         */
        @Override
        public void run() {
            while(true) {
                try {
                    if(!isRun) {
                        isRun = true;
                        checkChange(); //执行检测
                    }
                    Thread.sleep(checkTime);
                }catch(Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    
    /**
     * 脚本文件信息类
     * @author 马宝刚
     * 2015年10月15日
     */
    protected class ScriptFileBean {
    	public String id     = null; //脚本主键
        public long   update = 0;    //上次更新时间
        public long   time   = 0;    //检测时间
    }
    
    /**
     * 构造函数
     * @author 马宝刚
     */
    public ScriptCheckService() {
        super();
    }
    
    /**
     * 构造函数
     * @author MBG
     */
    public ScriptCheckService(IScriptLoader sl) {
    	super();
    	setBase(sl);
    	this.sl = sl;
    }
    
    /**
     * 启动检测
     * 2015年10月15日
     * @author 马宝刚
     */
    public void start() {
    	info("****** Start ScriptCheckService ******");
        ct = new CheckThread();
        ct.start();
    }
    
    /**
     * 设置是否允许执行检测任务
     * @param enabled 是否允许执行检测任务
     * 2020年8月25日
     * @author MBG
     */
    public void setEnabled(boolean enabled) {
    	this.enabled = enabled;
    }
    
    /**
     * 返回是否允许执行检测任务
     * @return 是否允许执行检测任务
     * 2020年8月25日
     * @author MBG
     */
    public boolean isEnabled() {
    	return enabled;
    }
    
    /**
     * 终止检测
     * 2015年10月15日
     * @author 马宝刚
     */
    @SuppressWarnings("deprecation")
    public void stop() {
        try {
            ct.stop();
        }catch(Exception e) {}
        ct = null;
        info("****** The ScriptCheckService Is Stoped ******");
    }
    
    /**
     * 设置检测间隔时间
     * @param time
     * 2015年10月15日
     * @author 马宝刚
     */
    public void setCheckTime(long time) {
        this.checkTime = time;
    }
    
    /**
     * 获取有效的脚本加载器
     * @return 有效的脚本加载器
     * @throws Exception 异常
     * 2015年10月15日
     * @author 马宝刚
     */
    public IScriptLoader getSl() throws Exception {
        if(sl==null) {
            sl = getBean(IScriptLoader.class);
        }
        return sl;
    }

    
    /**
     * 检测脚本文件是否发生变化
     * 2015年10月15日
     * @author 马宝刚
     */
    public void checkChange() throws Exception {
    	if(!enabled) {
    		return;
    	}
        long          time     = System.currentTimeMillis(); //检测时间
        IScriptLoader sl       = getSl();                    //获取脚本加载器
        String        basePath = sl.getSourceBasePath();     //脚本文件路径
        List<String>  pathList = new ArrayList<String>();    //文件路径序列
        
        //搜索文件
        SFilesUtil.getFileList(pathList,basePath,null,"xml",true,true);
        if(pathList.size()<1) {
        	//没有脚本源文件，终止执行
        	log("-------------Not Find The XML SourceFile CheckScriptService Has Stoped");
        	stop();
        	return;
        }
        boolean       isFirst  = fileMap.size() <= 0;//是否为首次收集信息
        
        ScriptFileBean sfb        = null; //文件信息类
        String         id         = null; //脚本文件主键
        long           updateTime = 0;    //文件更新时间
        String         subPath;           //脚本相对路径
        ScriptVO       sVO;               //脚本信息容器
        
        //循环检测文件是否发生变化
        for(String path:pathList) {
            id         = filesUtil.getFileBeforeName(path);
            updateTime = (new File(basePath+"/"+path)).lastModified();
            if("_DEFAULT".equals(id)) {
                //不处理文件夹说明信息
                continue;
            }
            //去掉文件名，只保留文件路径，在调用reBuildScript方法时，会拼装文件名。
            subPath = SFilesUtil.getFilePath(path,false);
            if(isFirst) {
                //构建新的信息类
                sfb        = new ScriptFileBean();
                sfb.id     = id;
                sfb.update = updateTime;
                sfb.time   = time;
                fileMap.put(path,sfb);
                continue;
            }
            
            sfb = fileMap.get(path);
            if(sfb==null) {
                if(!sl.hasScript(id)) {
                    //如果没有手工在管理台保存，脚本管理器中没有这个脚本
                    //就被认定为是复制过来的
                    log("<*>_<*>The Script:["+id+"] Has Append,Add Into The ScriptLoader");
                    //新增的脚本
                    sl.reBuildScript(id,subPath);
                }
                sfb        = new ScriptFileBean();
                sfb.id     = id;
                sfb.update = updateTime;
                sfb.time   = time;
                fileMap.put(path,sfb);
                continue;
            }
            if(sfb.update!=updateTime) {
                if(sl.hasScript(id)) {
                    sVO = sl.getScriptInfo(id);
                    if(sVO!=null && sVO.sourceFileTime==updateTime) {
                        //手工在管理台中保存的脚本，不是文件复制上来的
                        sfb.update = updateTime;
                        sfb.time   = time;
                        continue;
                    }
                }
                log("<*>_<*>The Script:["+id+"] Has Changed,Begin Rebuild");
                //文件时间发生变化
                sl.reBuildScript(id,subPath);
                sfb.update = updateTime;
                sfb.time   = time;
                continue;
            }
            sfb.time = time; //文件没发生变化
        }
        if(isFirst) {
        	//只有在启动时检测脚本是否被外部操作删除
        	//否则移动脚本后，就在原路径找不到这个脚本了，导致移动后的脚本被删除了
            //检测删除的脚本
            List<String> pList = BaseUtil.getMapKeyList(fileMap);
            for(String path:pList) {
            	if("_DEFAULT".equals(path)) {
            		continue;
            	}
                sfb = fileMap.get(path);
                if(sfb.time==time) {
                    continue;
                }
                log("<*>_<*>The Script:["+path+"] Has Deleted,Begin Deleted");
                //本次搜索文件，没有找到该文件，执行删除
                sl.deleteScript(sfb.id);
                fileMap.remove(path);
            }
        }
        isRun = false;
    }
}
